"""
Module to store link dependencies between the libraries compiled in a project.
"""

from . import compilersettings
from made.util import misc

# TODO: This doesn't feel right.  Find a way to clean it up.
class InputLibrary(object):
    def __init__(
            self, name, is_built, is_dynamic,
            full_path_to_static_library_or_none=None):
        # Note that on Linux the name includes the "lib" prefix and the
        # ".so"/".a" suffix.
        self.name = name
        self.is_built = is_built
        self.is_dynamic = is_dynamic
        self.full_path_to_static_library_or_none = (
            full_path_to_static_library_or_none)

    @property
    def file_needs_to_be_monitored(self):
        return self.is_built and not self.is_dynamic

    def __repr__(self):
        return "<InputLibrary name='%s' is_built='%s'>" % (
            self.name, self.is_built)

_unexpanded_lib_deps = {}
def set_unexpanded_lib_deps(unexpanded_lib_deps):
    """Store a dictionary mapping libraries that get compiled to the libraries
    they need to link against.

    For example, suppose this project builds libraries foo, bar, and baz.
    Further suppose that foo needs to link against the system library biz, bar
    needs to link against foo and biz, baz needs to link bar, foo, and biz.
    The dictionary would then be:
    {
        "foo": ["biz"],
        "bar": ["foo"],
        "baz": ["bar"],
    }
    Note the following:
    - "bar" doesn't need to explicitly link against "biz", since it links
      against "foo" (and "foo" links against "biz").  These link dependencies
      are "unexpanded" in the input dictionary, and will be expanded to
      explicitly list all input libraries at link time.
    - The build system knows that "biz" is a system library because it is not
      a key in the dictionary.
    """
    global _unexpanded_lib_deps
    _unexpanded_lib_deps = unexpanded_lib_deps.copy()

def input_libs_for_libs(lib_names, is_for_dynamic):
    """Return all the libraries that are needed as linker inputs for the
    given sequence of libraries.  The libraries returned includes the library
    names passed in.

    The result libraries are partitioned into two sequences: libraries that
    are built as part of the build process, and system libraries.  So, the
    return value is (input_built_libraries, input_system_libraries).

    This function allows a binary to only list the toplevel libraries it needs
    to link against, without listing them all.  Using the example dictionary in
    set_unexpanded_lib_deps, suppose a binary needs to link against "baz"
    (and all the libraries that "baz" needs).  if 'lib_names' is ["baz"], the
    return value would be (["baz", "bar", "foo"], ["biz"]).
    """
    result = list(lib_names)
    for lib_name in lib_names:
        result.extend(_expand_deps(lib_name))
    result = _unique_elements(result)

    # On non-MSVC builds, all libraries implicitly have "lib" in front of them.
    # Otherwise, only the built libraries do.
    cpp_compiler_chain = compilersettings.cpp_compiler_chain
    built_libs = set(_unexpanded_lib_deps.keys())
    result = [
        ("lib" + lib_name
            if compilersettings.cpp_compiler_chain.name != "msvc" or
                lib_name in built_libs
            else lib_name)
        for lib_name in result]

    lib_extension = (
        compilersettings.input_dynamic_library_extension() if is_for_dynamic
        else compilersettings.static_library_extension())

    return [
        InputLibrary(
            _add_extension_if_missing(lib_name, lib_extension),
            is_built=lib_name in built_libs,
            is_dynamic=is_for_dynamic)
        for lib_name in result]

def _add_extension_if_missing(name, extension):
    return (name + extension if len(misc.extension(name)) == 0 else name)

def _expand_deps(lib_name):
    result = [lib_name]
    for dep in _unexpanded_lib_deps.get(lib_name, ()):
        result.extend(_expand_deps(dep))
    return result

def _unique_elements(sequence):
    """Given a sequence, return a corresponding list with any duplicate
    entries removed.  The order is otherwise preserved.
    """
    seen_elements = set()
    result = []
    for element in sequence:
        if element not in seen_elements:
            result.append(element)
            seen_elements.add(element)
    return result

